package org.jiuling.local.tools.crypto;

import org.jiuling.local.tools.enums.CryptoAlgorithmEnum;
import org.jiuling.local.tools.exception.LocalToolsRuntimeException;
import org.jiuling.local.tools.random.UUID;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * RSA加密算法
 * @author 紫灵
 * @date 2022年08月02日 14时11分45秒
 */
@Deprecated
public class Rsa {

    /** 算法 */
    private final static CryptoAlgorithmEnum ALGORITHM = CryptoAlgorithmEnum.RSA;

    /**
     * 生成密钥
     * @param keysize 密钥长度
     * @return 密钥
     */
    public static SecretKey createSecretKey(int keysize){
        KeyPair keyPair = createKeyPair(keysize);
        // 获取公钥与私钥
        RSAPublicKey rsaPublicKey = (RSAPublicKey)keyPair.getPublic();
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey)keyPair.getPrivate();
        // 封装密钥
        SecretKey secretKey = new SecretKey();
        secretKey.setCheckCode(UUID.getUUIDIsWithoutLine());
        secretKey.setCryptoAlgorithm(ALGORITHM);
        secretKey.setPublicKey(Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded()));
        secretKey.setPrivateKey(Base64.getEncoder().encodeToString(rsaPrivateKey.getEncoded()));
        return secretKey;
    }

    /**
     * 生成密钥对
     * @param keysize 密钥长度
     * @return 密钥对
     */
    public static KeyPair createKeyPair(int keysize){
        // 生成密钥对
        KeyPairGenerator generator;
        try {
            generator = KeyPairGenerator.getInstance(ALGORITHM.getAlgorithm());
            generator.initialize(keysize);
        } catch (NoSuchAlgorithmException e) {
            throw new LocalToolsRuntimeException(e.getLocalizedMessage());
        }
        return generator.generateKeyPair();
    }

    /**
     * 生成密钥对
     * @return 密钥对
     */
    public static  KeyPair createKeyPair(){
        return createKeyPair(1024);
    }

    /**
     * 生成密钥
     * @return 密钥
     */
    public static SecretKey createSecretKey(){
        return createSecretKey(1024);
    }

    /**
     * 通过公钥加密
     * @param data 数据
     * @param publicKey 公钥
     * @return 加密数据
     */
    public static String encryptByPublicKey(String publicKey,String data) throws InvalidKeySpecException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
        Cipher cipher = getCipher();
        RSAPublicKey rsaPublicKey = getPublicKey(publicKey);
        cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
        // 模长n转换成字节数
        int modulusSize = rsaPublicKey.getModulus().bitLength() / 8;
        // PKCS Padding长度为11字节，所以实际要加密的数据不能要 - 11byte
        int maxSingleSize = modulusSize - 11;
        // 切分字节数组，每段不大于maxSingleSize
        byte[][] dataArray = splitArray(data.getBytes(), maxSingleSize);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // 分组加密，并将加密后的内容写入输出字节流
        for (byte[] s : dataArray) {
            out.write(cipher.doFinal(s));
        }
        // 使用Base64将字节数组转换String类型
        return Base64.getEncoder().encodeToString(out.toByteArray());
    }

    /**
     * 通过私钥解密
     * @param data 数据
     * @param privateKey 私钥
     * @return 解密后的数据
     */
    public static String decryptByPrivateKey(String privateKey,String data) throws InvalidKeySpecException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, IOException {
        Cipher cipher = getCipher();
        RSAPrivateKey rsaPrivateKey = getPrivateKey(privateKey);
        cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
        // RSA加密算法的模长 n
        int modulusSize = rsaPrivateKey.getModulus().bitLength() / 8;
        byte[] dataBytes = data.getBytes();
        // 之前加密的时候做了转码，此处需要使用Base64进行解码
        byte[] decodeData = Base64.getDecoder().decode(dataBytes);
        // 切分字节数组，每段不大于modulusSize
        byte[][] splitArrays = splitArray(decodeData, modulusSize);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        for (byte[] arr : splitArrays) {
            out.write(cipher.doFinal(arr));
        }
        return out.toString();
    }

    /**
     * 获取Cipher
     * @return Cipher对象
     */
    private static Cipher getCipher(){
        try {
            return Cipher.getInstance(ALGORITHM.getAlgorithm());
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new LocalToolsRuntimeException(e.getLocalizedMessage());
        }
    }

    /**
     * 获取RSA私钥
     * @param privateKey 私钥
     * @return RSA私钥
     */
    private static RSAPrivateKey getPrivateKey(String privateKey) throws InvalidKeySpecException {
        KeyFactory keyFactory = getKeyFactory();
        byte[] keyBytes = Base64.getDecoder().decode(privateKey);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        return (RSAPrivateKey) keyFactory.generatePrivate(spec);
    }

    /**
     * 获取RSA公钥
     * @param publicKey 公钥
     * @return RSA公钥
     */
    private static RSAPublicKey getPublicKey(String publicKey) throws InvalidKeySpecException {
        KeyFactory keyFactory = getKeyFactory();
        byte[] keyBytes = Base64.getDecoder().decode(publicKey);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        return (RSAPublicKey) keyFactory.generatePublic(spec);
    }

    /**
     * 获取key工厂
     * @return key工厂
     */
    private static KeyFactory getKeyFactory(){
        try {
            return KeyFactory.getInstance(ALGORITHM.getAlgorithm());
        } catch (NoSuchAlgorithmException e) {
            throw new LocalToolsRuntimeException(e.getLocalizedMessage());
        }
    }


    /**
     * 按指定长度切分数组
     * @param data
     * @param len 单个字节数组长度
     * @return
     */
    private static byte[][] splitArray(byte[] data,int len){

        int dataLen = data.length;
        if (dataLen <= len) {
            return new byte[][]{data};
        }
        byte[][] result = new byte[(dataLen-1)/len + 1][];
        int resultLen = result.length;
        for (int i = 0; i < resultLen; i++) {
            if (i == resultLen - 1) {
                int slen = dataLen - len * i;
                byte[] single = new byte[slen];
                System.arraycopy(data, len * i, single, 0, slen);
                result[i] = single;
                break;
            }
            byte[] single = new byte[len];
            System.arraycopy(data, len * i, single, 0, len);
            result[i] = single;
        }
        return result;
    }

}
